package org.shalicon.chip
package pipeline

import chisel3._
import utils.{FrontendIO, Parameter}

import chisel3.util.{DecoupledIO, Queue}

class FetchBundle(implicit p: Parameter) extends ShBundle()(p) {
  val pc = UInt(VADDR_WIDTH)
  val insts = Vec(p.fetch_count, UInt(INST_WIDTH))
  val mask = Bits(width = p.fetch_count.W)
  val fetch_exception = Bool()
  val fetch_replay = Bool()
}
class FetchUnit(implicit p: Parameter) extends ShModule {
  val io = IO(new ShBundle()(p) {
    val imem = new FrontendIO()
    val tsc = Input(UInt(XLEN))
    val flush_fetchBuffer = Input(Bool())
    val ftake_pc = Input(Bool())
    val flush_pc_target = Input(UInt(VADDR_WIDTH))

    val stalled = Output(Bool())
    val resp = DecoupledIO(new FetchBundle())
  })

  val fseq_reg = RegInit(0.U(XLEN))
  val fetch_buffer = Module(new Queue(gen = new FetchBundle, entries = p.fetch_bs, pipe = false, flow = true, hasFlush = true))

  val fetch_bundle = Wire(new FetchBundle())
  val next_pc = Wire(UInt(VADDR_WIDTH))

  // if fetchBuffer is full, the instruction fetch action should be stalled
  val if_stalled = !fetch_buffer.io.enq.ready

  val take_pc = io.ftake_pc || !if_stalled

  // memory ready-valid signals
  io.imem.req.valid := take_pc
  io.imem.req.bits.pc := next_pc
  io.imem.req.bits.speculative := !io.ftake_pc
  io.imem.resp.ready := !if_stalled

  next_pc := Mux(io.ftake_pc, io.flush_pc_target, Mux(
    // todo: for bp use
  ))

  fetch_buffer.io.enq.valid := io.imem.resp.valid && !io.flush_fetchBuffer
  fetch_buffer.io.enq.bits := fetch_bundle

  fetch_bundle.pc := io.imem.resp.bits.pc
  fetch_bundle.fetch_exception := io.imem.resp.bits.fetch_exception
  fetch_bundle.fetch_replay := io.imem.resp.bits.fetch_replay

  for (i <- 0 until p.fetch_count) {
    fetch_bundle.insts(i) := io.imem.resp.bits.data(i)
    // todo: debug port
  }
  // todo: btb

  // output
  io.stalled := if_stalled
  io.resp <> io.resp
}
